<?php

declare(strict_types=1);

namespace core;

use Exception;
use core\ErrorHandler;
use RuntimeException;

/**
 * 应用程序核心类
 */
final class App
{
    /** @var string 应用的基础 URL */
    private string $base_url;

    /** @var string 应用的物理根路径 */
    private string $root_path;

    /** @var string 应用存放控制器的路径 */
    private string $app_path;

    /** @var Router 路由解析器实例 */
    private Router $router;

    /** @var array<string, mixed> 应用程序配置数组 */
    private array $config;

    /** @var Request 请求对象实例 */
    private Request $request;

    /** @var View|null 视图实例(延迟加载) */
    private ?View $view = null;

    private $dbInstance = null;

    /**
     * 初始化应用程序实例
     *
     * @param Request $request 当前的请求对象 [新增依赖]
     * @param string $base_url 应用程序的基础 URL
     * @param string $app_path 应用程序的物理根路径，用于文件加载
     * @param string $root_path 根路径
     * @throws Exception 当配置加载失败时抛出异常
     */
    public function __construct(
        Request $request,
        string $base_url  = BASE_URL,
        string $root_path = ROOT_PATH,
        string $app_path  = APP_PATH,
    ) {
        $this->request   = $request;
        $this->base_url  = $base_url;
        $this->root_path = $root_path;
        $this->app_path  = $app_path;
        $this->config    = $this->loadConfig();

        $this->router = new Router($this->config, $request);
    }

    /**
     * 运行应用程序
     *
     * @return void
     * @throws Exception 当控制器加载失败或方法不存在时抛出异常
     */
    public function run(): void
    {
        if ($this->config('session_enable', false)) {
            $this->startSession();
        }

        AppException::register($this->config('debug'));

        $this->dispatch();
    }

    /**
     * 负责加载控制器、验证方法并执行。
     *
     * @return void
     * @throws Exception 当控制器加载失败或方法不存在时抛出异常
     */
    private function dispatch(): void
    {
        $controller = $this->loadController($this->router->getControllerName());
        $method     = $this->router->getMethodName();

        if (!method_exists($controller, $method)) {
            throw new Exception(
                "Method '{$method}' not found in controller '{$this->router->getControllerName()}'.", 404
            );
        }

        // 提前检查 before 和 after 方法是否存在
        $has_before = method_exists($controller, '_before');
        $has_after  = method_exists($controller, '_after');

        // 执行 before 方法（如果存在）
        if ($has_before) {
            $controller->_before();
        }

        $controller->{$method}();

        // 执行 after 方法（如果存在）
        if ($has_after) {
            $controller->_after();
        }
    }

    /**
     * 获取数据库封装类的实例
     *
     * 遵循懒加载模式，仅在首次调用时创建数据库连接。
     * @return Database 数据库操作封装实例
     * @throws \Exception 如果数据库配置缺失或无效，则抛出异常
     */
    public function getDb(): Database
    {
        if ($this->dbInstance === null) {
            $conf = $this->config('database');

            $this->dbInstance = new Database($conf);
        }

        return $this->dbInstance;
    }

    /**
     * 加载主配置文件。
     *
     * 从 app/config/app.php 加载配置数组
     * 如果文件不存在,返回空数组
     *
     * @return array<string, mixed> 配置数组
     */
    private function loadConfig(): array
    {
        $config_path = $this->root_path . '/config/app.php';
        if (!is_file($config_path)) {
            throw new RuntimeException("Config file not found: $config_path");
        }

        return require $config_path;
    }

    /**
     * 获取配置值。
     *
     * 支持点语法嵌套访问，如 'database.host'。
     * 如果 key 为 null,返回全部配置
     *
     * @param string|null $key 配置键，null 表示返回全部
     * @param mixed $default 默认值
     * @return mixed 配置值或默认值
     */
    public function config(?string $key = null, mixed $default = null): mixed
    {
        // 返回全部配置
        if ($key === null) {
            return $this->config;
        }

        // 简单键访问
        if (strpos($key, '.') === false) {
            return $this->config[$key] ?? $default;
        }

        // 点语法嵌套访问
        $keys = explode('.', $key);
        $value = $this->config;

        foreach ($keys as $segment) {
            if (!is_array($value) || !array_key_exists($segment, $value)) {
                return $default;
            }
            $value = $value[$segment];
        }

        return $value;
    }

    /**
     * 获取视图实例(单例模式)
     */
    public function view(): View
    {
        if ($this->view === null) {
            $viewPath  = $this->config('view.path', APP_PATH . '/view');
            $cachePath = $this->config('view.cache_path', RUNTIME_PATH . '/view');
            $this->view = new View($viewPath, $cachePath);
        }
        return $this->view;
    }

    /**
     * 加载并实例化控制器
     *
     * 约定:
     * - 控制器文件位于 app/controller/ 目录
     * - 文件名首字母大写,格式为 {Name}.php
     * - 类名与文件名一致,位于 app\controller 命名空间
     * - 控制器类必须继承自 core\Controller
     *
     * @param string $controller_name 控制器名称
     * @return Controller 控制器实例
     * @throws Exception 当控制器文件不存在、类不存在或未继承 Controller 时抛出异常
     */
    private function loadController(string $controller_name): Controller
    {
        // 约定：控制器文件名首字母大写
        $file_name = ucfirst($controller_name) . '.php';
        $file_path = $this->app_path . '/controller/' . $file_name;

        if (!is_file($file_path)) {
            throw new Exception("Controller file not found: {$file_path}");
        }

        require_once $file_path;

        // 约定：控制器类名与文件名一致，位于 app\controller 命名空间下
        $class_name = 'app\\controller\\' . ucfirst($controller_name);

        if (!class_exists($class_name)) {
            throw new Exception("Controller class not found: {$class_name}");
        }

        // 实例化控制器并注入 App 实例
        $controller = new $class_name($this, $this->view(), $this->getDb());

        if (!$controller instanceof Controller) {
            throw new Exception("Class '{$class_name}' must extend from core\\Controller.");
        }

        return $controller;
    }

    /**
     * 获取或设置 Session 值
     *
     * @param string|null $key Session 键名，null 表示获取全部
     * @param mixed $value 要设置的值（可选）
     * @return mixed 对应的值、全部 session 数组，或 null
     */
    public function session(?string $key = null, mixed $value = null): mixed
    {
        $this->startSession(); // 按需启动 + 安全初始化

        $numArgs = func_num_args();

        if ($key !== null && $numArgs === 2) {
            // 用户传了至少两个参数，视为"设置"
            $_SESSION[$key] = $value;
            return null;
        }

        if ($key === null) {
            return $_SESSION;
        }

        return $_SESSION[$key] ?? null;
    }

    /**
     * 安全地启动 Session（仅启动一次）
     *
     * 使用配置中的 session 设置（如果提供），否则使用默认安全配置。
     */
    private function startSession(): void
    {
        if (session_status() !== PHP_SESSION_NONE) {
            return; // 已启动，直接返回
        }

        $session_config = $this->config('session', []);

        // 默认安全配置
        $default_options = [
            'cookie_lifetime' => 1800,       // Session ID Cookie 存活时间（秒）[30分钟]
            'cookie_secure' => false,        // 仅在 HTTPS 下传输
            'cookie_httponly' => true,       // 阻止 JS 访问（并非所有浏览器都支持）
            'cookie_samesite' => 'Strict',   // 强大的 CSRF 防御
            'use_strict_mode' => true,       // 启用严格 session id 模式
            'use_only_cookies' => true,      // 禁止 URL 传递 session_id
            'sid_length' => 48,              // 更长的 session ID
            'gc_maxlifetime' => 1800,        // 与 cookie_lifetime 保持一致
        ];

        // 合并用户配置
        $options = array_merge($default_options, $session_config);

        session_start($options);
    }

    /**
     * 获取路由实例
     *
     * @return Router 路由实例
     */
    public function getRouter(): Router
    {
        return $this->router;
    }

    /**
     * 获取 Request 实例
     *
     * @return Request 请求实例
     */
    public function getRequest(): Request
    {
        return $this->request;
    }
}
